/**
 * Copyright (c) 2011, Todd Ginsberg
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the name of Todd Ginsberg, or Gowalla nor the
 *      names of any contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 *  Also, please use this for Good and not Evil.  
 */
package com.ginsberg.gowalla.request.translate;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import com.ginsberg.gowalla.dto.Button;
import com.ginsberg.gowalla.dto.HighlightType;
import com.ginsberg.gowalla.dto.Id;
import com.ginsberg.gowalla.dto.Item;
import com.ginsberg.gowalla.dto.Pin;
import com.ginsberg.gowalla.dto.ReceiptComponent;
import com.ginsberg.gowalla.dto.ReceiptComponentType;
import com.ginsberg.gowalla.dto.User;
import com.ginsberg.gowalla.util.Strings;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

/**
 * This ONLY exists because "heading" can be an array sometimes and a string other times.
 * This confuses GSON (and me, frankly) so we have to custom deserialize everything
 * or resort to screwy custom types inside ReceiptComponent (something like a StringOrArrayOfStrings type)
 * in order to deal with it.  Since I'd rather not do that, here we are.  
 * 
 * @author Todd Ginsberg
 */

class ReceiptComponentDeserializer implements JsonDeserializer<ReceiptComponent> {

	@Override
	public ReceiptComponent deserialize(final JsonElement json, final Type type, final JsonDeserializationContext ctx) throws JsonParseException {
		final ReceiptComponent c = new ReceiptComponent();
		
		// This is what we came here for.
		setHeadings(json, c);
		setType(json, c);
		
		// Everything else can be deserialized naturally.  Unfortunately there's no way to tell
		// GSON to just do that or else we'll end up in an infinite loop of calls to this
		// method.  So, we do it manually...
		
		// String translations.
		c.setImageUrl(stringOf(json, "image_url", null));
		c.setMessage(stringOf(json, "message", null));
		c.setDurationInDays(intOf(json, "duration_in_days", 0));
		
		// Object translations.
		setButtons(json, c, ctx);
		setHighlightType(json, c, ctx);
		setItem(json, c, ctx);
		setPin(json, c, ctx);
		setUsers(json, c, ctx);
		setCreator(json, c, ctx);
		
		return c;
	}
	
	private JsonElement childElement(final JsonElement json, final String key) {
		final JsonObject obj = (JsonObject)json;
		return obj.get(key);
	}
	
	private int intOf(final JsonElement json, final String key, final int def) {
		final JsonObject obj = (JsonObject)json;
		return obj.has(key) ? obj.get(key).getAsInt() : def;
	}

	private String stringOf(final JsonElement json, final String key, final String def) {
		final JsonObject obj = (JsonObject)json;
		return obj.has(key) ? obj.get(key).getAsString() : def;
	}
	
	private void setCreator(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		JsonElement creator = null;
		if(c.getType() == ReceiptComponentType.NOTE) {
			creator = childElement(json, "creator");
		} else if(c.getType() == ReceiptComponentType.HIGHLIGHT) {
			creator = childElement(json, "user");
		} else {
			return;
		}
		User u = (User)ctx.deserialize(creator, User.class);
		fixId(u);
		c.setCreator(u);

	}
	
	private void setPin(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		if(c.getType() == ReceiptComponentType.PIN) {
			Pin p = (Pin)ctx.deserialize(json, Pin.class);
			fixId(p);
			c.setPin(p);
		}
	}
	
	private void setItem(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		if(c.getType() == ReceiptComponentType.ITEM) {
			Item i = (Item)ctx.deserialize(json, Item.class);
			fixId(i);
			c.setItem(i);
		}
	}
	
	private void setUsers(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		if(c.getType() == ReceiptComponentType.LEADERBOARD) {
			final JsonElement users = childElement(json, "users");
			if(users != null && users.isJsonArray()) {
				for(JsonElement e : users.getAsJsonArray()) {
					User u = (User)ctx.deserialize(e, User.class);
					fixId(u);
					c.addUser(u);
				}
			}
		}
	}
	
	private void setHighlightType(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		final JsonElement highlight = childElement(json, "highlight_type");
		if(highlight != null) {
			c.setHighlightType((HighlightType)ctx.deserialize(highlight, HighlightType.class));
		}
	}	
	
	private void setButtons(final JsonElement json, final ReceiptComponent c, final JsonDeserializationContext ctx) {
		final JsonElement buttons = childElement(json, "buttons");
		if(buttons != null && buttons.isJsonArray()) {
			for(JsonElement e : buttons.getAsJsonArray()) {
				c.addButton((Button)ctx.deserialize(e, Button.class));
			}
		}
	}
	
	private void fixId(Id<?> i) {
		i.setId(Strings.toId(i.getUrl()));	
	}
	
	/**
	 * @param json
	 * @param c
	 */
	private void setHeadings(final JsonElement json, final ReceiptComponent c) {
		final JsonObject obj = (JsonObject)json;
		if(obj.has("heading")) {
			final JsonElement heading = obj.get("heading");
			if(heading.isJsonArray()) {
				final List<String> headings = new LinkedList<String>();
				for(JsonElement e : heading.getAsJsonArray()) {
					headings.add(e.getAsString());
				}
				c.setHeadings(headings);
			} else {
				c.setHeadings(Collections.singletonList(heading.getAsString()));
			}
		}
	}
	
	private void setType(final JsonElement json, final ReceiptComponent c) {
		final JsonObject obj = (JsonObject)json;
		if(obj.has("type")) {
			c.setType(ReceiptComponentType.typeForValue(obj.get("type").getAsString()));
		} else {
			c.setType(ReceiptComponentType.OTHER);
		}
	}
}
